基于长短时神经网络(LSTM)+word2vec的情感分析

您所在的位置:网站首页 lstm 情感分析 基于长短时神经网络(LSTM)+word2vec的情感分析

基于长短时神经网络(LSTM)+word2vec的情感分析

2023-07-16 12:18| 来源: 网络整理| 查看: 265

前言: 毕业前的项目,最近终于有时间整理个博客出来。使用的keras+gensim完成,也参考了互联网很多相关资料。最终效果只有88%左右,不过优化空间很大,只用作学习demo

数据集使用的是谭松波酒店评论数据集 停用词我自己整理了一个停用词词典 分享给大家 链接:https://pan.baidu.com/s/1ZkMGAUH7VSxJALWBs41iKQ 提取码:2c1e

1.数据处理

这一步主要是对评论文本做清洗,在这里只做简单的去停用词。 首先写一个去停用词的方法

import jieba f = open('./stop_words.txt', encoding='utf-8') # 加载停用词 stopwords = [i.replace("\n", "") for i in f.readlines()] # 停用词表 def del_stop_words(text): """ 删除每个文本中的停用词 :param text: :return: """ word_ls = jieba.lcut(text) word_ls = [i for i in word_ls if i not in stopwords] return word_ls

然后读取正面评论与负面评论的语料 并进行清洗

with open("./test_data/neg.txt", "r", encoding='UTF-8') as e: # 加载负面语料 neg_data1 = e.readlines() with open("./test_data/pos.txt", "r", encoding='UTF-8') as s: # 加载正面语料 pos_data1 = s.readlines() neg_data = sorted(set(neg_data1), key=neg_data1.index) #列表去重 保持原来的顺序 pos_data = sorted(set(pos_data1), key=pos_data1.index) neg_data = [del_stop_words(data.replace("\n", "")) for data in neg_data] # 处理负面语料 pos_data = [del_stop_words(data.replace("\n", "")) for data in pos_data] all_sentences= neg_data + pos_data # 全部语料 用于训练word2vec 2. 文本向量化

对于文本的向量化其实有很多方式,包括独热(one-hot),词袋模型(bag of words),逆文本特征频率(tf-idf)和word2vec等。

本项目我们使用word2vec(据说效果很好)进行词向量的提取,word2vec是使用深度学习的方式将词映射为一个多维向量。

首先顶级模型结构 并进行训练及保存

from gensim.models.word2vec import Word2Vec from gensim.corpora.dictionary import Dictionary import pockle import logging logging.basicConfig(format='%(asctime)s : %(levelname)s : %(message)s', level=logging.INFO) # 将日志输出到控制台 model = Word2Vec(all_sentences, # 上文处理过的全部语料 size=150, # 词向量维度 默认100维 min_count=1, # 词频阈值 词出现的频率 小于这个频率的词 将不予保存 window=5 # 窗口大小 表示当前词与预测词在一个句子中的最大距离是多少 ) model.save('./models/Word2vec_v1') # 保存模型

然后加载模型 提取出词的索引与向量

def create_dictionaries(model): """ 创建词语字典,并返回word2vec模型中词语的索引,词向量 """ gensim_dict = Dictionary() # 创建词语词典 gensim_dict.doc2bow(model.wv.vocab.keys(), allow_update=True) w2indx = {v: k + 1 for k, v in gensim_dict.items()} # 词语的索引,从1开始编号 w2vec = {word: model[word] for word in w2indx.keys()} # 词语的词向量 return w2indx, w2vec model = Word2Vec.load('./models/Word2vec_v1') # 加载模型 index_dict, word_vectors= create_dictionaries(model) # 索引字典、词向量字典

使用 pickle 存储序列化数据 pickle是一个非常方便的库 可以将py的字典 列表等等程序运行过程中的对象存储为实体数据

output = open(pkl_name + ".pkl", 'wb') pickle.dump(index_dict, output) # 索引字典 pickle.dump(word_vectors, output) # 词向量字典 output.close() 3. LSTM训练

接下来 使用keas库搭建LSTM模型 来进行训练 首先我们定义几个必要参数

# 参数设置 vocab_dim = 150 # 向量维度 maxlen = 150 # 文本保留的最大长度 batch_size = 100 # 训练过程中 每次传入模型的特征数量 n_epoch = 4 # 迭代次数

加载词向量数据 并填充词向量矩阵

f = open("./model/评价语料索引及词向量2.pkl", 'rb') # 预先训练好的 index_dict = pickle.load(f) # 索引字典,{单词: 索引数字} word_vectors = pickle.load(f) # 词向量, {单词: 词向量(100维长的数组)} n_symbols = len(index_dict) + 1 # 索引数字的个数,因为有的词语索引为0,所以+1 embedding_weights = np.zeros((n_symbols, 150)) # 创建一个n_symbols * 100的0矩阵 for w, index in index_dict.items(): # 从索引为1的词语开始,用词向量填充矩阵 embedding_weights[index, :] = word_vectors[w] # 词向量矩阵,第一行是0向量(没有索引为0的词语,未被填充)

接下来将所有的评论数据映射成为数字 因为之前通过加载词向量 已经拥有了一个索引字典 只要将出现在的索引字典中的单词转换为其索引数字 未出现的转换为0即可

def text_to_index_array(p_new_dic, p_sen): """ 文本或列表转换为索引数字 :param p_new_dic: :param p_sen: :return: """ if type(p_sen) == list: new_sentences = [] for sen in p_sen: new_sen = [] for word in sen: try: new_sen.append(p_new_dic[word]) # 单词转索引数字 except: new_sen.append(0) # 索引字典里没有的词转为数字0 new_sentences.append(new_sen) return np.array(new_sentences) # 转numpy数组 else: new_sentences = [] sentences = [] p_sen = p_sen.split(" ") for word in p_sen: try: sentences.append(p_new_dic[word]) # 单词转索引数字 except: sentences.append(0) # 索引字典里没有的词转为数字0 new_sentences.append(sentences) return new_sentences

加载特征与标签 将特征全部映射成数字 并且分割验证集和测试集

with open("./原始语料/neg.txt", "r", encoding='UTF-8') as f: neg_data1 = f.readlines() with open("./原始语料/pos.txt", "r", encoding='UTF-8') as g: pos_data1 = g.readlines() neg_data = sorted(set(neg_data1), key=neg_data1.index) #列表去重 保持原来的顺序 pos_data = sorted(set(pos_data1), key=pos_data1.index) neg_data = [process_txt(data) for data in neg_data] pos_data = [process_txt(data) for data in pos_data] data = neg_data + pos_data # 读取语料类别标签 label_list = ([0] * len(neg_data) + [1] * len(pos_data)) # 划分训练集和测试集,此时都是list列表 X_train_l, X_test_l, y_train_l, y_test_l = train_test_split(data, label_list, test_size=0.2) # 转为数字索引形式 # token = Tokenizer(num_words=3000) #字典数量 # token.fit_on_texts(train_text) X_train = text_to_index_array(index_dict, X_train_l) X_test = text_to_index_array(index_dict, X_test_l) y_train = np.array(y_train_l) # 转numpy数组 y_test = np.array(y_test_l) print("训练集shape: ", X_train.shape) print("测试集shape: ", X_test.shape)

因为模型输入的每一个特征长度需要相同,所以我们需要定义一个最大的长度max_len。 当特征小于max_len时,根据max_len填充其余位数为0。 当特征大于max_len,则进行截断。 在本项目中,我定义的max_len为150,是一个平均长度。有时候为了保证不丢失信息,可以打印出所有特征中最大的长度,并将其设置为max_len

X_train = sequence.pad_sequences(X_train, maxlen=maxlen) X_test = sequence.pad_sequences(X_test, maxlen=maxlen)

定义模型 训练模型 验证模型

ef train_lstm(p_n_symbols, p_embedding_weights, p_X_train, p_y_train, p_X_test, p_y_test, X_test_l): print('创建模型...') model = Sequential() model.add(Embedding(output_dim=vocab_dim, # 输出向量维度 input_dim=p_n_symbols, # 输入向量维度 mask_zero=True, # 使我们填补的0值在后续训练中不产生影响(屏蔽0值) weights=[p_embedding_weights], # 对数据加权 input_length=maxlen )) # 每个特征的长度 model.add(LSTM(output_dim=100, activation='sigmoid', inner_activation='hard_sigmoid')) model.add(Dropout(0.5)) # 每次迭代丢弃50神经元 防止过拟合 model.add(Dense(units=512, activation='relu')) model.add(Dropout(0.5)) model.add(Dense(units=1, # 输出层1个神经元 1代表正面 0代表负面 activation='sigmoid')) model.summary() print('编译模型...') model.compile(loss='binary_crossentropy', optimizer='adam', metrics=['accuracy']) print("训练...") train_history = model.fit(p_X_train, p_y_train, batch_size=batch_size, nb_epoch=n_epoch, validation_data=(p_X_test, p_y_test)) print("评估...") score, acc = model.evaluate(p_X_test, p_y_test, batch_size=batch_size) label = model.predict(p_X_test) print('Test score:', score) print('Test accuracy:', acc) for (a, b, c) in zip(p_y_test, X_test_l, label): print("原文为:"+ "".join(b)) print("预测倾向为", a) print("真实倾向为", c) show_train_history(train_history, 'acc', 'val_acc') # 训练集准确率与验证集准确率 折线图 show_train_history(train_history, 'loss', 'val_loss') # 训练集误差率与验证集误差率 折线图 """保存模型""" model.save('./model/emotion_model_LSTM.h5') print("模型保存成功")

可以通过show_train_history函数打印的训练集曲线 来判断模型是否过拟合。 方便确定迭代次数 进行调参 函数如下

def show_train_history(train_history,train, velidation): """ 可视化训练过程 对比 :param train_history: :param train: :param velidation: :return: """ plt.plot(train_history.history[train]) plt.plot(train_history.history[velidation]) plt.title("Train History") #标题 plt.xlabel('Epoch') #x轴标题 plt.ylabel(train) #y轴标题 plt.legend(['train', 'test'], loc='upper left') #图例 左上角 plt.show()

项目github:https://github.com/sph116/lstm_emotion 没仔细检查,可能会有些小问题,望海涵,欢迎交流。 在这里插入图片描述

后续优化: 1.增大训练word2vec 语料数量 2.数据清洗不止进行简单的去停用词 3.增加模型结构及复杂度



【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3